using System;
using System.Collections.Generic;
using System.Text;
using MotionBlur.Butler.Runtime;
using MotionBlur.Butler.Model;
using System.Threading;

namespace MotionBlur.Butler.Triggers
{

    public class ScheduledMonitor : ITriggerMonitor
    {

        public const int PollingInterval = 100;

        private struct TriggerOcurrence
        {

            public Guid OrderID;
            public ScheduledTrigger Trigger;
            public DateTime TimeStamp;
            public TriggerCallback Callback;

        }

        private bool _Enabled;
        private Timer _Timer;
        private DateTime _LastStopStamp;
        private Dictionary<Guid, TriggerOcurrence> _NextOcurrences;

        public ScheduledMonitor()
        {

            _Enabled = false;
            
            _NextOcurrences = new Dictionary<Guid, TriggerOcurrence>();

        }

        public void StartMonitor()
        {

            _LastStopStamp = DateTime.Now;

            TimerCallback pollingCallback = new TimerCallback(this.PollingCallback);

            _Timer = new Timer(pollingCallback);

            _Enabled = true;

            this.TryStartPolling();

        }

        public void StopMonitor()
        {

            this.TryStopPolling();

            _Enabled = false;

            _Timer.Dispose();

        }

        public bool CanRegister(DutyTrigger trigger)
        {

            return (trigger is ScheduledTrigger);

        }

        public void RegisterTrigger(DutyTrigger trigger, Guid orderID, TriggerCallback callback)
        {

            this.TryStopPolling();

            ScheduledTrigger scheduledTrigger = trigger as ScheduledTrigger;

            TriggerOcurrence? ocurrence = this.GetNextOcurrence(scheduledTrigger, orderID, callback);

            if (ocurrence.HasValue)
            {

                //TODO: lock some sync object to organize the modifications to the dictionary.

                _NextOcurrences.Add(trigger.TriggerID, ocurrence.Value);

            }

            this.TryStartPolling();

        }

        public void UnregisterTrigger(DutyTrigger trigger)
        {

            this.TryStopPolling();

            if (_NextOcurrences.ContainsKey(trigger.TriggerID))
            {

                //TODO: lock some sync object to organize the modifications to the dictionary.

                _NextOcurrences.Remove(trigger.TriggerID);

            }

            this.TryStartPolling();

        }

        private bool TryStartPolling()
        {

            if (!_Enabled)
            {
                return false;
            }

            _Timer.Change(0, ScheduledMonitor.PollingInterval);

            return true;

        }

        private bool TryStopPolling()
        {

            if (!_Enabled)
            {
                return false;
            }

            _Timer.Change(-1, Timeout.Infinite);

            _LastStopStamp = DateTime.Now;

            return true;

        }

        private void PollingCallback(object state)
        {

            this.TryStopPolling();

            List<Guid> triggerIDs = new List<Guid>(_NextOcurrences.Keys);

            foreach (Guid triggerID in triggerIDs)
            {

                TriggerOcurrence ocurrence = _NextOcurrences[triggerID];

                if (ocurrence.TimeStamp <= _LastStopStamp)
                {

                    this.FireOcurrence(ocurrence);

                    TriggerOcurrence? nextOcurrence = this.GetNextOcurrence(ocurrence.Trigger, ocurrence.OrderID, ocurrence.Callback);

                    if (nextOcurrence.HasValue)
                    {
                        _NextOcurrences[triggerID] = nextOcurrence.Value;
                    }
                    else
                    {
                        _NextOcurrences.Remove(triggerID);
                    }

                }

            }

            this.TryStartPolling();

        }

        private void FireOcurrence(TriggerOcurrence ocurrence)
        {

            Dictionary<string, object> parameters = new Dictionary<string, object>();

            //TODO: load standard parameters

            ocurrence.Callback(ocurrence.OrderID, parameters);

        }

        private TriggerOcurrence? GetNextOcurrence(ScheduledTrigger trigger, Guid orderID, TriggerCallback callback)
        {

            TimeSpan? spanToNext = trigger.GetSpanToNextOccurence(_LastStopStamp);

            if (!spanToNext.HasValue)
            {

                return null;

            }

            TriggerOcurrence ocurrence = new TriggerOcurrence();

            ocurrence.OrderID = orderID;
            ocurrence.Trigger = trigger;
            ocurrence.Callback = callback;
            ocurrence.TimeStamp = _LastStopStamp.Add(spanToNext.Value);

            return ocurrence;

        }

    }

}
